#include "PageCach.h"

PageCach PageCach::sInst_;

//根据地址,计算该地址所处页号, 返回idSpanMap中页号所映射的span
Span* PageCach::MapObjectToSpan(void* obj) {
    PAGE_ID page_id = (PAGE_ID)obj / PAGE_SIZE;
    std::unique_lock<std::mutex> ulock(pageMtx_);//出作用域自动解锁
    assert(idSpanMap_.count(page_id));
    return  idSpanMap_[page_id];
}

//获取一个k页的span给Central Cach
Span* PageCach::NewSpan(size_t k) {
    assert(k > 0);
    //大于128页的直接向堆申请
    if (k > NPAGES - 1) {
        Span* span = spanPoll.New();
        span->freelist_ = SystemAlloc(k);//向堆申请k页大小内存
        span->pageId_   = ((PAGE_ID)span->freelist_) / PAGE_SIZE;//地址偏移量/一个页的字节 = 页号
        span->n_ = k;
        idSpanMap_[span->pageId_] = span;//页号 --> span 映射存好
        return span;
    }

    //看一下第k号桶里有没有span
    if (!spanList_[k].Empty()) {
        Span* span = spanList_[k].Pop();
        for (PAGE_ID id = 0; id < span->n_; ++id) {
            idSpanMap_[span->pageId_ + id] = span;
        }
        return span;//删除并返回第一个span节点
    }
    //检查更大的桶里面有没有span, 做切分
    for (int i = k + 1; i < NPAGES; ++i) {
        if (!spanList_[i].Empty()) {
            Span* nSpan = spanList_[i].Pop();//先从spanlist断开
            Span* kSpan = spanPoll.New();
            kSpan->n_  = k;//k页
            nSpan->n_ -= k;
            kSpan->pageId_   = nSpan->pageId_;//从头切,所以页号是这个
            nSpan->pageId_  += k;
            kSpan->useCount_ = nSpan->useCount_ = 0;
            spanList_[i - k].Push(nSpan);//把切后剩余的i-k页span链入对应桶

            //存储nspan的首尾页号, 方便page Cach回收进行合并查找
            idSpanMap_[nSpan->pageId_] = nSpan;
            idSpanMap_[nSpan->pageId_ + nSpan->n_ - 1] = nSpan;
            //记录页号 与 Span对象的映射, 方便内存回收
            for (PAGE_ID id = 0; id < kSpan->n_; ++id) {
                idSpanMap_[kSpan->pageId_ + id] = kSpan;
            }
            return kSpan;
        }
    }
    //没有更大页的span,只能从系统分配,找堆要128kb
    Span*   bigSpan    = spanPoll.New();
    bigSpan->freelist_ = SystemAlloc(NPAGES - 1);
    bigSpan->pageId_   = ((PAGE_ID)bigSpan->freelist_) / PAGE_SIZE;//地址偏移量/一个页的字节 = 页号
    bigSpan->n_ = NPAGES - 1;
    bigSpan->useCount_ = 0;
    bigSpan->isUse_ = false;
    spanList_[NPAGES - 1].Push(bigSpan);
    idSpanMap_[bigSpan->pageId_] = bigSpan;
    idSpanMap_[bigSpan->pageId_ + bigSpan->n_ - 1] = bigSpan;
    return NewSpan(k);
}

void PageCach::ReleaseSpanToPageCach(Span* span) {
    //大于128页的直接还给堆
    if (span->n_ > NPAGES - 1) {
        SystemFree(span->freelist_, span->n_);
        spanPoll.Delete(span);
        return;
    }
    //对span前后的页 尝试进行合并, 解决外碎片的问题
    //向前合并
    while (true) {
        PAGE_ID preid = span->pageId_ - 1;
        if (idSpanMap_.count(preid) == 0) break;//页不存在
        else if (idSpanMap_[preid]->isUse_ == true) break;//前面的页在被Cnetral Cach使用
        else if (span->n_ + idSpanMap_[preid]->n_ > NPAGES - 1) break;//合起来太大,超过NPAGES阈值
        else {
            Span* preSpan = idSpanMap_[preid];
            spanList_[preSpan->n_].Erase(preSpan);//从span链表弹出
            span->n_ += preSpan->n_;
            span->pageId_ = preSpan->pageId_;
            spanPoll.Delete(preSpan);
        }
    }
    //向后合并
    while (true) {
        PAGE_ID nextid = span->pageId_ + span->n_;
        if (idSpanMap_.count(nextid) == 0) break;//页不存在
        else if (idSpanMap_[nextid]->isUse_ == true) break;//前面的页在被Cnetral Cach使用
        else if (span->n_ + idSpanMap_[nextid]->n_ > NPAGES - 1) break;//合起来太大,超过NPAGES阈值
        else {
            Span* nextSpan = idSpanMap_[nextid];
            spanList_[nextSpan->n_].Erase(nextSpan);//从span链表弹出
            span->n_ += nextSpan->n_;
            spanPoll.Delete(nextSpan);
        }
    }
    span->isUse_ = false;
    span->freelist_ = (void*)(span->pageId_ * PAGE_SIZE); //页号 * 一页大小 = 地址
    idSpanMap_[span->pageId_] = span;
    idSpanMap_[span->pageId_ + span->n_ - 1] = span;
    spanList_[span->n_].Push(span);
}